﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents a Givens rotation matrix, which is primary used for QR decompression.
    /// </summary>
    [Serializable]
    public class GivensRotationMatrix : Matrix
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GivensRotationMatrix"/> class.
        /// </summary>
        /// <param name="dimension">The dimension of the Givens rotation matrix G(i,k,a).</param>
        /// <param name="row">The row parameter k for the Givens rotation matrix G(i,k,a).</param>
        /// <param name="column">The column parameter i for the Givens rotation matrix G(i,k,a).</param>
        /// <param name="rotationAngle">The rotation angle a of the Givens rotation matrix.</param>
        public GivensRotationMatrix(int dimension, int row, int column, double rotationAngle)
            : base(dimension, dimension)
        {
            if (column >= dimension)
            {
                throw new ArgumentException("column >= dimension");
            }

            if (row >= dimension)
            {
                throw new ArgumentException("row >= dimension");
            }

            for (int j = 0; j < dimension; j++)
            {
                this.MatrixData[j, j] = 1;
            }

            this.MatrixData[row, row] = Math.Cos(rotationAngle);
            this.MatrixData[column, column] = Math.Cos(rotationAngle);
            this.MatrixData[column, row] = Math.Sin(rotationAngle);
            this.MatrixData[row, column] = -Math.Sin(rotationAngle);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GivensRotationMatrix"/> class.
        /// </summary>
        /// <param name="dimension">The dimension of the Givens rotation matrix G(i,k,a).</param>
        /// <param name="row">The row parameter k for the Givens rotation matrix G(i,k,a).</param>
        /// <param name="column">The column parameter i for the Givens rotation matrix G(i,k,a).</param>
        /// <param name="c">The value for the cosine parameter c.</param>
        /// <param name="s">The value for the sine parameter s.</param>
        public GivensRotationMatrix(int dimension, int row, int column, double c, double s)
            : base(dimension, dimension)
        {
            if (column >= dimension)
            {
                throw new ArgumentException("column >= dimension");
            }

            if (row >= dimension)
            {
                throw new ArgumentException("row >= dimension");
            }

            for (int j = 0; j < dimension; j++)
            {
                this.MatrixData[j, j] = 1;
            }

            this.MatrixData[row, row] = c;
            this.MatrixData[column, column] = c;
            this.MatrixData[column, row] = s;
            this.MatrixData[row, column] = -s;
        }

        /// <summary>
        /// Creates a Givens rotation matrix from a specified matrix.
        /// </summary>
        /// <param name="row">The row index, which should be set to zero.</param>
        /// <param name="column">The column index, which should be set to zero.</param>
        /// <param name="matrix">The specified matrix.</param>
        /// <returns>The created Givens rotation matrix.</returns>
        public static GivensRotationMatrix CreateFromMatrix(int row, int column, Matrix matrix)
        {
            if (matrix == (Matrix) null)
            {
                throw new ArgumentNullException("matrix");
            }

            double r = Math.Sign(matrix.GetValueAtPosition(column, column))
                       * Math.Sqrt(Math.Pow(matrix.GetValueAtPosition(column, column), 2)
                                   + Math.Pow(matrix.GetValueAtPosition(row, column), 2));

            if (r == 0)
            {
                GivensRotationMatrix errorMatrix = new GivensRotationMatrix(matrix.Rows, row, column, 1, 1);

                for (int i = 0; i < errorMatrix.Rows; i++)
                {
                    for (int j = 0; j < errorMatrix.Columns; j++)
                    {
                        errorMatrix.SetValueAtPosition(i, j, 1);
                    }
                }
            }

            double c = matrix.GetValueAtPosition(column, column) / r;
            double s = matrix.GetValueAtPosition(row, column) / r;

            return new GivensRotationMatrix(matrix.Rows, row, column, c, s);
        }

        /// <summary>
        /// Creates a Givens rotation matrix from a specified matrix.
        /// </summary>
        /// <param name="row">The row index, which should be set to zero.</param>
        /// <param name="column">The column index, which should be set to zero.</param>
        /// <param name="matrix">The specified matrix.</param>
        /// <returns>The created Givens rotation matrix.</returns>
        public static GivensRotationMatrix CreateFromMatrix(int row, int column, ref Matrix matrix)
        {
            if (matrix == (Matrix) null)
            {
                throw new ArgumentNullException("matrix");
            }

            double r = Math.Sign(matrix.GetValueAtPosition(column, column))
                       * Math.Sqrt(Math.Pow(matrix.GetValueAtPosition(column, column), 2)
                                   + Math.Pow(matrix.GetValueAtPosition(row, column), 2));

            if (r == 0)
            {
                GivensRotationMatrix errorMatrix = new GivensRotationMatrix(matrix.Rows, row, column, 1, 1);

                for (int i = 0; i < errorMatrix.Rows; i++)
                {
                    for (int j = 0; j < errorMatrix.Columns; j++)
                    {
                        errorMatrix.SetValueAtPosition(i, j, 1);
                    }
                }
            }

            double c = matrix.GetValueAtPosition(column, column) / r;
            double s = matrix.GetValueAtPosition(row, column) / r;

            return new GivensRotationMatrix(matrix.Rows, row, column, c, s);
        }
    }
}